iT邦幫忙

2026 iThome 鐵人賽

DAY 6
0
Software Development

從 Laravel 到 Spring Boot:30 天打造縮網址服務系列 第 6

Day6 - 請求驗證 Validation vs Laravel Form Request

  • 分享至 

  • xImage
  •  

Laravel Form Request 怎麼運作

Laravel 的驗證邏輯通常寫在獨立的 FormRequest 類別裡:

class CreateLinkRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'url' => ['required', 'url'],
        ];
    }
}

// Controller
public function store(CreateLinkRequest $request)
{
    $validated = $request->validated();
    // 驗證已經跑完,這裡拿到的一定是合法資料
}

關鍵在於:光是把參數型別寫成 CreateLinkRequest,驗證就會自動觸發——Laravel 的 Service Container 解析這個型別時,會先跑 rules() 檢查,驗證失敗直接短路,Controller 方法本體根本不會被執行,還會自動回傳 422 跟結構化的錯誤訊息,整個過程不用寫任何一行額外的程式碼。

Spring Boot:Bean Validation

Spring Boot 沒有獨立的「Form Request」類別,驗證規則直接標在 DTO 的欄位上

record CreateLinkRequest(
    @NotBlank(message = "url 不能為空")
    @URL(message = "url 格式不正確")
    String url
) {}

Controller 這邊要明確加上 @Valid,驗證才會真的被觸發:

@PostMapping
ResponseEntity<LinkResponse> store(@Valid @RequestBody CreateLinkRequest request) {
    // 驗證通過才會執行到這裡
    ...
}

這裡有兩個容易踩的雷:

  1. @NotBlank@URL 這些驗證註解不是 Spring 內建的,要在 pom.xml 額外加 spring-boot-starter-validation 依賴才能用(回顧 Day03:Maven 的依賴要自己宣告,不像 PHP 擴充套件很多是隨語言內建)
  2. 忘記加 @Valid 是最常見的雷——欄位上的驗證註解寫了,但 Controller 參數沒加 @Valid,驗證直接被跳過,程式碼看起來完全沒問題,卻悄悄失去保護

另外,@URL 嚴格來說不是 Jakarta Bean Validation 規範本身的註解,是 Hibernate Validator(Spring Boot 預設用的 Bean Validation 實作)額外提供的擴充驗證,這也是為什麼查官方 Jakarta 規範文件會找不到它,要查 Hibernate Validator 的文件才有。

驗證失敗長什麼樣(先看原始版本)

Laravel 驗證失敗,框架自動回一個整理好的 422:

{
    "message": "The url field is required.",
    "errors": {
        "url": ["The url field is required."]
    }
}

Spring Boot 目前(還沒處理過)丟出的是 MethodArgumentNotValidException,預設的錯誤回應長這樣,明顯比較原始、內部細節外露:

{
    "timestamp": "2026-09-20T10:00:00.000+00:00",
    "status": 400,
    "errors": [
        {
            "field": "url",
            "defaultMessage": "url 不能為空"
        }
    ]
}

這個「原始版本」先讓大家看一次,是刻意的——Spring Boot 預設不會像 Laravel 一樣自動幫你把錯誤回應包得漂漂亮亮,要透過 @ControllerAdvice + @ExceptionHandler 自己攔截、自己整理格式,這部分完整會在 Day10(例外處理與統一錯誤回應) 展開,這裡先知道「預設長這樣、之後會被整理」就好。

自訂驗證規則對照

如果內建的驗證規則不夠用(例如要驗證「這個短碼有沒有被使用過」),Laravel 用一個 Rule 類別就能搞定:

class UniqueLinkCode implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (Link::where('code', $value)->exists()) {
            $fail('這個短碼已經被使用過了');
        }
    }
}

Bean Validation 要寫兩個東西才能做到同樣的事:一個自訂註解 + 一個實作驗證邏輯的 Validator 類別:

@Constraint(validatedBy = UniqueLinkCodeValidator.class)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface UniqueLinkCode {
    String message() default "這個短碼已經被使用過了";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

class UniqueLinkCodeValidator implements ConstraintValidator<UniqueLinkCode, String> {
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        // 查資料庫邏輯,Day07 接上 Repository 後才能真的實作
        return true;
    }
}

明顯比 Laravel 囉唆不少——但換來的好處是這個自訂驗證規則變成一個「型別」,可以像 @NotBlank 一樣直接標在任何欄位上重複使用,而且編譯期就能檢查有沒有標錯地方,這是 Laravel 的字串 Rule 名稱做不到的。


上一篇
Day5 - Spring MVC Controller/Routing vs Laravel Route/Controller
下一篇
Day7 - Spring Data JPA vs Eloquent ORM 基礎
系列文
從 Laravel 到 Spring Boot:30 天打造縮網址服務9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言